package com.berchen.Channel;

import org.junit.Test;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

/**
 * 通道：由java.nio.channels包定义的，Channel表示IO源与目标之间的连接。Channel类似于“流”，只不过Channel本身不能直接访问数据，Channel只能与Buffer进行交互。
 *  用于源节点与目标节点的连接。在JAVA NIO 中负责缓冲区中数据的传输。通道本身是不存储数据的。因此需要配合缓冲区进行操作。
 *
 *
 *  通道的主要实现类：
 *      java.nio.channels.Channel 接口：
 *          |--FileChannel：本地IO。
 *          |--SocketChannel：网络IO。
 *          |--ServerSocketChannel：网络IO。
 *          |--DatagramChannel：网络IO。
 *
 * 获取通道：
 *  1、Java针对支持通道的类提供了getChannel()方法。
 *      本地IO：
 *      FileInputStream/FileOutputStream
 *      RandomAccessFile
 *
 *      网络IO：
 *      Socket
 *      ServerSocket
 *      DatagramSocket
 *
 *  2、JDK1.7中的NIO2针对各个通道提供了一个静态方法FileChannel.open()。
 *  3、JDK1.7中的NIO2 的一个Files工具类的newByteChannel()方法。
 *
 *
 * 通道之间的数据传输： 直接从一个通道传递到另外一个通道。不需要缓冲区。
 *  1、transferFrom()
 *  2、transferTo()
 *
 * 分散（Scatter）于聚集（Gather）：
 * 分散读取（Scattering Reads）：将通道中的数据分散到多个缓冲区中。
 * 聚集写入（Gathering Writes）：将多个缓冲区中的数据聚集到通道中。
 *
 * 字符集（Charset）：
 *  编码：字符串转换成字节数组。
 *  解码：字节数组转成字符串。
 *
 *
 */
public class TestChannel {

    @Test
    public void test6() throws CharacterCodingException {

        // 设置编码
        Charset gbk = Charset.forName("GBK");

        // 获取编码器与解码器
        CharsetEncoder encoder = gbk.newEncoder();  // 编码器
        CharsetDecoder decoder = gbk.newDecoder();  // 解码器

        CharBuffer cb = CharBuffer.allocate(1024);
        cb.put("陈琬皓");  // 存储数据到缓存区
        cb.flip();

        // 编码
        ByteBuffer encode = encoder.encode(cb); // 编码

        byte[] b=new byte[encode.limit()];
        encode.get(b);
        System.out.println(new String(b,0,encode.limit())); // 打印编码之后的结果

        encode.flip();

        // 解码
        CharBuffer decode = decoder.decode(encode); // 解码
        System.out.println(decode.toString());

    }

    @Test
    public void test5(){

        // 查看支持多少种字符集
        SortedMap<String, Charset> map = Charset.availableCharsets();
        Set<Map.Entry<String, Charset>> entries = map.entrySet();
        for(Map.Entry<String,Charset> m:entries){

            System.out.println(m.getValue());
        }
    }


    @Test
    public void test4() throws IOException {

        RandomAccessFile raf1=new RandomAccessFile("D:\\JavaProject\\Project\\All_Project\\Others_Project\\JavaNIO\\src\\1.txt","rw");

        // 获取通道
        FileChannel channel = raf1.getChannel();

        // 分配指定大小的缓冲区
        ByteBuffer buf1=ByteBuffer.allocate(100);
        ByteBuffer buf2=ByteBuffer.allocate(1024);

        // 分数读取
        ByteBuffer[] byteBuffers={buf1,buf2};
        channel.read(byteBuffers);  // 将通道中的数据读取到多个缓冲区中。

        // 读取缓冲区
        Arrays.stream(byteBuffers).forEach(x->x.flip());    // 将数组中的缓冲区转成读模式
        System.out.println(new String(byteBuffers[0].array(),0,byteBuffers[0].limit()));
        System.out.println("----------------------------------");
        System.out.println(new String(byteBuffers[1].array(),0,byteBuffers[1].limit()));

        // 聚集写入
        RandomAccessFile raf2=new RandomAccessFile("D:\\JavaProject\\Project\\All_Project\\Others_Project\\JavaNIO\\src\\2.txt","rw");
        FileChannel channel2 = raf2.getChannel();

        // 聚集写入
        channel2.write(byteBuffers);    // 将多个缓冲区中的数据写到通道中（通道连接的是文件）。

        channel.close();
        channel2.close();
    }

    @Test
    public void test3() throws IOException {

        // 通道之间的数据传输（直接缓冲区）
        FileChannel inChannel = FileChannel.open(
                Paths.get("D:\\JavaProject\\Project\\All_Project\\Others_Project\\JavaNIO\\src\\1.jpg"),
                StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(
                Paths.get("D:\\JavaProject\\Project\\All_Project\\Others_Project\\JavaNIO\\src\\4.jpg"),
                StandardOpenOption.WRITE,
                StandardOpenOption.READ,    // 给outChannel加一个读模式，因为outMapBuf是一个读写模式，outChannel没有读模式就会报错
                StandardOpenOption.CREATE   // 如果3.jpg不存在就创建，如果存在就覆盖。CREATE_NEW (存在就报错)
        );

        // 数据传输,两个选一个都可以
//        inChannel.transferTo(0,inChannel.size(),outChannel);
        outChannel.transferFrom(inChannel,0,inChannel.size());

        inChannel.close();
        outChannel.close();
    }

    // 直接缓冲区，（内存映射文件）   11ms 更快
    @Test
    public void test2() throws IOException {

        Instant start = Instant.now();
        FileChannel inChannel = FileChannel.open(
                Paths.get("D:\\JavaProject\\Project\\All_Project\\Others_Project\\JavaNIO\\src\\1.jpg"),
                StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(
                Paths.get("D:\\JavaProject\\Project\\All_Project\\Others_Project\\JavaNIO\\src\\3.jpg"),
                StandardOpenOption.WRITE,
                StandardOpenOption.READ,    // 给outChannel加一个读模式，因为outMapBuf是一个读写模式，outChannel没有读模式就会报错
                StandardOpenOption.CREATE   // 如果3.jpg不存在就创建，如果存在就覆盖。CREATE_NEW (存在就报错)
        );

        // 获取内存映射文件 和 allocateDirect方法作用一样，获取直接缓冲区。
        MappedByteBuffer inMapBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMapBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());


        // 直接对缓冲区进行数据的读写操作
        byte[] dst=new byte[inMapBuf.limit()];
        inMapBuf.get(dst);  // 读取数据
        outMapBuf.put(dst); // 写数据

        inChannel.close();
        outChannel.close();

        Instant end = Instant.now();
        Duration between = Duration.between(start, end);
        System.out.println(between.toMillis());

    }

    // 非直接缓冲区 利用通道完成文件的复制   20ms
    // 是通道的read write方法进行读写数据（写到缓冲区再从缓冲区读）。
    @Test
    public void test1() throws IOException {

        Instant start = Instant.now();
        FileInputStream fis=new FileInputStream("D:\\JavaProject\\Project\\All_Project\\Others_Project\\JavaNIO\\src\\1.jpg");
        FileOutputStream fos=new FileOutputStream("D:\\JavaProject\\Project\\All_Project\\Others_Project\\JavaNIO\\src\\2.jpg");

        // 获取通道
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();

        // 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024); // 非直接缓冲区

        // 将通道中的数据存到缓存中去

        while (inChannel.read(buf)!=-1){    // 从in通道中读取数据，放到缓冲区中

            // 切换成读取数据模式
            buf.flip();         // 将缓冲区编程读取模式

            // 将缓冲区中的数据写到通道
            outChannel.write(buf);
            buf.clear();    // 清空缓冲区，将position归位。
        }

        outChannel.close();
        inChannel.close();
        fos.close();
        fis.close();

        Instant end = Instant.now();

        Duration between = Duration.between(start, end);
        System.out.println(between.toMillis());
    }
}
